﻿using Bouyei.Geo.Converters;
using Bouyei.Geo.Geometries;
using Bouyei.Geo.GeoParsers.Wkb;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Bouyei.Geo.GeoParsers.PostGIS
{
    public class GeometryReader:BaseBytes
    {
        public GeoType GeometryType { get; set; }

        public int SRID { get; private set; } = -1;

        public Ordinates Ordinates { get; private set; }

        public GeometryReader(byte[] buffer,int offset=0)
            :base(buffer,offset)
        {

        }

        public unsafe Geometry Reader()
        {
            Geometry geo = null;
            fixed (byte* ptr = &Buffer[Offset+1])
            {
                int flag=ReadHeader(ptr);

                byte* start = ptr + flag;

                switch (GeometryType)
                {
                    case GeoType.POINT:
                        {
                            var point = new WkbPoint(bitExt, start);
                            geo = new Geometry(point.Coord);
                            break;
                        }
                    case GeoType.LINESTRING:
                        {
                            var linestring = new WkbLineString(bitExt, start);
                            geo = new Geometry(GeoType.LINESTRING, linestring.GetCoordinates());
                        }
                        break;
                    case GeoType.POLYGON:
                        {
                            var polygon = new WkbPolygon(bitExt, start);
                            if (polygon.Count == 1) geo = new Geometry(GeoType.POLYGON, polygon[0].Coords);
                            else if (polygon.Count > 1) geo = new Geometry(polygon[0].Coords, polygon.GetInteriorRing());
                        }
                        break;
                    case GeoType.MULTIPOINT:
                        {
                            var multipoint = ReadeMultiPoint(bitExt,start);// new WkbMultiPoint(bitExt, start);
                            geo = new Geometry(GeoType.MULTIPOINT, multipoint.GetCoordinates());
                            break;
                        }
                    case GeoType.MULTILINESTRING:
                        {
                            var multilinestring =  ReadMultiLine(bitExt,start);// new WkbMultiLineString(bitExt, start);
                            geo = new Geometry(GeoType.MULTILINESTRING, multilinestring.GetCoordinates());
                        }
                        break;
                    case GeoType.MULTIPOLYGON:
                        {
                            var multiPolygon = ReadMultiPolygon(bitExt,start);// new WkbMultiPolygon(bitExt, start);
                            List<GeoPolygon> polygons = new List<GeoPolygon>(multiPolygon.Count);

                            foreach (WkbPolygon polygon in multiPolygon)
                            {
                                if (polygon.Count == 1)
                                    polygons.Add(new GeoPolygon(polygon.GetOuterRing()));
                                else if (polygon.Count >= 2)
                                    polygons.Add(new GeoPolygon(polygon.GetOuterRing(), polygon.GetInteriorRing()));
                            }
                            geo = new Geometry(polygons.ToArray());
                        }
                        break;
                    default:
                        throw new Exception("geometryType not supported" + GeometryType);
                }
            }
            geo.SRID = SRID;
            return geo;
        }

        private unsafe WkbMultiPoint ReadeMultiPoint(BitExtensions bitConvert, byte* ptr)
        {
            int start = 4;
            int Count = bitConvert.ToInt32(ptr);
            List<WkbPoint> ps = new List<WkbPoint>(Count);

            for (int i = 0; i < Count; ++i)
            {
                bitConvert.ByteOrder = (ByteOrder)(*(ptr + start));
                start += 1;

                ps.AddRange(new WkbMultiPoint(bitConvert, ptr+start).Points);
                start += 20;

                //int cnt = bitConvert.ToInt32(ptr+start);
                //start += 4;

                //for (int j = 0; j < cnt; ++j)
                //{
                //    ps.Add(new WkbPoint(bitConvert, ptr + start));
                //    start += 16;
                //}
            }
            return new WkbMultiPoint()
            {
                Points = ps.ToArray(),
                Count = ps.Count
            };
        }

        private unsafe WkbMultiLineString ReadMultiLine(BitExtensions bitConvert,byte* ptr)
        {
            int Count = bitConvert.ToInt32(ptr);
            int start = 4;
         
            List<WkbLineString> ps = new List<WkbLineString>(Count);

            for (int i = 0; i < Count; ++i)
            {
                bitConvert.ByteOrder = (ByteOrder)(*(ptr + start));
                start += 1;
                //geometry type 未使用，一般是linestring
                int geotype = bitConvert.ToInt32(ptr + start);
                start += 4;

                var line = new WkbLineString(bitConvert, ptr + start);

                ps.Add(line);
                start += (line.Count * 16 + 4);
            }
            return new WkbMultiLineString()
            {
                MultiLineString=ps,
                Count = ps.Count
            };
        }

        private unsafe WkbMultiPolygon ReadMultiPolygon(BitExtensions bitConvert,byte* ptr)
        {
            int Count = bitConvert.ToInt32(ptr);
            int start = 4;
            
            List<WkbPolygon> ps = new List<WkbPolygon>(Count);

            for (int i = 0; i < Count; ++i)
            {
                bitConvert.ByteOrder = (ByteOrder)(*(ptr + start));
                start += 1;
                //geometry type 未使用，一般是polygon
                int geotype = bitConvert.ToInt32(ptr + start);
                start += 4;

                var polygon = new WkbPolygon(bitConvert, ptr + start);
                ps.Add(polygon);

                int offset = polygon.Offset;
                start += (offset);
            }
            return new WkbMultiPolygon()
            {
                MultiPolygon = ps,
                Count = ps.Count
            };
        }

        private unsafe int ReadHeader(byte* ptr)
        {
            int typeword = bitExt.ToInt32(ptr);
            int flag = 4;

            GeometryType = (GeoType)(typeword & 0x1FFFFFFF);
            var ordinates = Ordinates.XY;

            if ((typeword & 0x80000000) != 0)
            {
                ordinates |= Ordinates.Z;
            }

            if ((typeword & 0x40000000) != 0)
            {
                ordinates |= Ordinates.M;
            }

            if ((typeword & 0x20000000) != 0)
            {
                SRID = bitExt.ToInt32(ptr + 4);
                flag += 4;
            }
            else
            {
                SRID = -1;
            }
            Ordinates = ordinates;

            return flag;
        }
    }
}
